`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: uofa
// Engineer: Andrew Gallasch
// 
// Create Date:    13:14:59 09/17/2013 
// Design Name: 
// Module Name:    timer 
// Project Name: uofa-mips
// Target Devices: uofa-mips
// Tool versions: 
// Description: 
//
// Dependencies: 
//
// Revision: 
// Revision 0.01 - File Created
// Additional Comments: 
//
//
// This module counts up from zero to a value supplied by MIPS then fires an interrupt when it's done.
// This must be turned OFF in the interrupt handler (in the MIPSprogram).
// This is done by using the timer again from a different value or by writing 0 to the timer.
//
// This module takes a parameter n that determines the number of timers to instantiate.
// The maximum number is 12.
//
// Each address from 0 4'b0000 to 11 4'b1011 refers to a unique independant timer.
//
// address 12 4'b1100 contains repeat enables. These instruct the timer to repeat.
// address 13 4'b1101 contains interrupt enables. Set these high if you want interrupts to be fired when the counter rolls-over.
// address 14 4'b1110 contains the id of the timer that caused the interrupt. (The timer that expired)
// address 15 4'b1111 contains the DRP_ID (as usual, as described in the DRP protocol.)
// 
//
//
//	i		| mask addr	| bitmask of repeats, interruptEnables , interrupts	
//	0		|  0x0		| 0x001						
//	1		|  0x4		| 0x002						
//	2		|  0x8		| 0x004						
//	3		|  0xc		| 0x008						
//	4		| 0x10		| 0x010						
//	5		| 0x14		| 0x020						
//	6		| 0x18		| 0x040						
//	7		| 0x1c		| 0x080						
//	8		| 0x20		| 0x100						
//	9		| 0x24		| 0x200						
//	10		| 0x28		| 0x400						
//	11		| 0x2c		| 0x800						
// r		| 0x30
// intEn	| 0x34
// int	| 0x38
// drp	| 0x3c
//////////////////////////////////////////////////////////////////////////////////

module timer
(	 input clk,
    input reset,
    input enable,
    input [3:0] address,
    input [31:0] dataIn,
    output [31:0] dataOut,
    input write,
    output done,
    output interrupt,
	 input clk_to_count // equal or slower than clk.
    );
	 
	 parameter n = 12;
	 
	 localparam DRP_TIMER_ID = 32'hA004001A; //
	 
	 localparam a_repeats				= 12;
	 localparam a_interruptEnables	= 13;
	 localparam a_interrupts			= 14;
	 localparam a_drp						= 15;
	 
	 reg [n-1:0] repeats;
	 reg [n-1:0] interruptEnables;
	 reg [n-1:0] interrupts;
	 
	 // assign interrupt = 1 if any interrupt is high and it is enabled.
	 //assign interrupt = |(interrupts[n-1:0] & interruptEnables[n-1:0]); // mask interrupts by enables.
	 assign interrupt = ( interrupts[n-1:0] != 0 );
	 
	 // the count. goes up til it reaches interval.
	 reg [31:0] count		[n-1:0];
	 reg [31:0] interval	[n-1:0];
	 
	 assign done = 1; // NEVER stall the MIPS processor when reading or writing to this peripheral.
	 // read/write will never take more than 1 cycle.
	 
	 reg dataOut_reg = 0;
	 assign dataOut = dataOut_reg;
	 
	 always @( posedge clk , posedge reset )
	 begin
			
			if( reset )
			begin
				repeats <= {n{1'b0}};
				interruptEnables <= {n{1'b1}};
				dataOut_reg <= 0;
			end
			else if( enable & write )
			begin
				if( address == a_repeats ) repeats <= dataIn[n-1:0];
				if( address == a_interruptEnables ) interruptEnables <= dataIn[n-1:0];
				dataOut_reg <= 0;
			end
			else if( enable & ~write ) // if reading.
			begin
				case( address )
					a_repeats			: dataOut_reg <= repeats;
					a_interruptEnables: dataOut_reg <= interruptEnables;
					a_interrupts		: dataOut_reg <= interrupts;
					// this gives the ids of interrupting timer to the MIPS. (LSB = timer0)
					a_drp					: dataOut_reg <= DRP_TIMER_ID;
					default: dataOut_reg <= count[ address ]; // return the amount of time left.
				endcase
			end
	 end
		
	 genvar i;
	 generate
		 for( i = 0 ; i < n ; i = i + 1 )
		 begin: clockrun
				 always @( posedge clk , posedge reset )
				 begin
						if( reset ) // RESET
						begin
								// set for every timer
								interval[i] <= 0;
								interrupts[i] <= 0;
								count[i] <= 32'b0;
						end
						else
						begin
							// NORMAL OPERATION
							if( interval[i] == 0 )
							begin
								interrupts[i] <= 0;
								count[i] <= 0;
							end
							else
							begin
								// reading / running normally. i.e. ticking.
								if( count[i] + 1 == interval[i] )
								begin
									count[i] <= repeats[i]?32'b0:interval[i]; // count loops if repeats
									interval[i] <= repeats[i] ? interval[i] : 32'b0;
									interrupts[i] <= interruptEnables[i];
								end
								else if( count[i] < interval[i] )
								begin
									// still counting, or looped after repeating.
									count[i] <= count[i] + 1;
								end
								else
								begin
									// something went wrong...
									count[i] <= 0;
									interrupts[i] <= 0;
									if( ~repeats[i] ) interval[i] <= 0;
								end
							end
							
							if( enable )
							begin
									// reading or writing to interrupts turns them ALL off.
									if( address == a_interrupts )
									begin
										interrupts[i] <= 1'b0;
									end
									
									if ( address == i )
									begin
											if( write )
											begin
												// standard WRITE
												interrupts[i] <= 1'b0;
												count[i] <= 32'b0;
												interval[i] <= dataIn;
											end
									end
							end // end enable
						end // ~reset
						//interrupts[i] <= interrupts[i] & interruptEnables[i];
						// set interrupt low if not enabled.
				 end // always
		  end // loop
	endgenerate
endmodule